#pragma once

#include <RcppArmadillo.h>
using namespace Rcpp;

/**
 * @brief Indicator wrapper, single output vector
 *
 * @tparam T type of the indicator
 * @tparam TO type of the indicator output
 */
template <typename T, typename TO = NumericVector>
struct indi1
{
    /**
     * @brief Create an indicator external pointer
     *
     * @tparam Args argument types
     * @param args ctor arguments
     * @return SEXP
     */
    template <typename... Args>
    static SEXP create(Args... args)
    {
        T *ptr = new T(args...);
        return XPtr<T>(ptr, true);
    }

    /**
     * @brief Run an indicator external pointer
     *
     * @tparam Arg1 first input type
     * @tparam Args remaining input types
     * @param ptr an XPtr to indicator
     * @param arg1 first input
     * @param args remaining inputs
     * @return TO
     */
    template <typename Arg1, typename... Args>
    static TO run(SEXP ptr, const Arg1 &arg1, const Args &...args)
    {
        XPtr<T> c(ptr);
        TO y(arg1.length());

        c->operator()(arg1.begin(), arg1.end(), args.begin()..., y.begin());
        return y;
    }

    /**
     * @brief Create an indicator
     *
     * @tparam Args argument types
     * @param args ctor arguments
     * @return T
     */
    template <typename... Args>
    static T create_t(Args... args)
    {
        return T(args...);
    }

    /**
     * @brief Run an indicator
     *
     * @tparam Arg1 first input type
     * @tparam Args remaining input types
     * @param c an indicator
     * @param arg1 first input
     * @param args remaining inputs
     * @return TO
     */
    template <typename Arg1, typename... Args>
    static TO run_t(T &c, const Arg1 &arg1, const Args &...args)
    {
        TO y(arg1.length());
        c(arg1.begin(), arg1.end(), args.begin()..., y.begin());
        return y;
    }
};

template <typename T, typename TO1 = NumericVector, typename TO2 = TO1>
struct indi2
{
    /**
     * @brief Create an indicator external pointer
     *
     * @tparam Args argument types
     * @param args ctor arguments
     * @return SEXP
     */
    template <typename... Args>
    static SEXP create(Args... args)
    {
        T *ptr = new T(args...);
        return XPtr<T>(ptr, true);
    }

    /**
     * @brief Run an indicator external pointer
     *
     * @tparam Arg1 first input type
     * @tparam Args remaining input types
     * @param ptr an XPtr to indicator
     * @param arg1 first input
     * @param args remaining inputs
     * @return List
     */
    template <typename Arg1, typename... Args>
    static List run(SEXP ptr, const Arg1 &arg1, const Args &...args)
    {
        XPtr<T> c(ptr);
        R_xlen_t n = arg1.length();
        TO1 y1(n);
        TO2 y2(n);

        c->operator()(arg1.begin(), arg1.end(), args.begin()..., y1.begin(), y2.begin());
        return List::create(y1, y2);
    }

    /**
     * @brief Create an indicator
     *
     * @tparam Args argument types
     * @param args ctor arguments
     * @return T
     */
    template <typename... Args>
    static T create_t(Args... args)
    {
        return T(args...);
    }

    /**
     * @brief Run an indicator
     *
     * @tparam Arg1 first input type
     * @tparam Args remaining input types
     * @param c an indicator
     * @param arg1 first input
     * @param args remaining inputs
     * @return List
     */
    template <typename Arg1, typename... Args>
    static List run_t(T &c, const Arg1 &arg1, const Args &...args)
    {
        R_xlen_t n = arg1.length();
        TO1 y1(n);
        TO2 y2(n);

        c(arg1.begin(), arg1.end(), args.begin()..., y1.begin(), y2.begin());
        return List::create(y1, y2);
    }
};

template <typename T, typename TO1 = NumericVector, typename TO2 = TO1, typename TO3 = TO1>
struct indi3
{
    /**
     * @brief Create an indicator external pointer
     *
     * @tparam Args argument types
     * @param args ctor arguments
     * @return SEXP
     */
    template <typename... Args>
    static SEXP create(Args... args)
    {
        T *ptr = new T(args...);
        return XPtr<T>(ptr, true);
    }

    /**
     * @brief Run an indicator external pointer
     *
     * @tparam Arg1 first input type
     * @tparam Args remaining input types
     * @param ptr an XPtr to indicator
     * @param arg1 first input
     * @param args remaining inputs
     * @return List
     */
    template <typename Arg1, typename... Args>
    static List run(SEXP ptr, const Arg1 &arg1, const Args &...args)
    {
        XPtr<T> c(ptr);
        R_xlen_t n = arg1.length();
        TO1 y1(n);
        TO2 y2(n);
        TO3 y3(n);

        c->operator()(arg1.begin(), arg1.end(), args.begin()..., y1.begin(), y2.begin(), y3.begin());
        return List::create(y1, y2, y3);
    }

    /**
     * @brief Create an indicator
     *
     * @tparam Args argument types
     * @param args ctor arguments
     * @return T
     */
    template <typename... Args>
    static T create_t(Args... args)
    {
        return T(args...);
    }

    /**
     * @brief Run an indicator
     *
     * @tparam Arg1 first input type
     * @tparam Args remaining input types
     * @param c an indicator
     * @param arg1 first input
     * @param args remaining inputs
     * @return List
     */
    template <typename Arg1, typename... Args>
    static List run_t(T &c, const Arg1 &arg1, const Args &...args)
    {
        R_xlen_t n = arg1.length();
        TO1 y1(n);
        TO2 y2(n);
        TO3 y3(n);

        c(arg1.begin(), arg1.end(), args.begin()..., y1.begin(), y2.begin(), y3.begin());
        return List::create(y1, y2, y3);
    }
};

template <typename T, typename TO1 = NumericVector, typename TO2 = TO1, typename TO3 = TO1, typename TO4 = TO1>
struct indi4
{
    /**
     * @brief Create an indicator external pointer
     *
     * @tparam Args argument types
     * @param args ctor arguments
     * @return SEXP
     */
    template <typename... Args>
    static SEXP create(Args... args)
    {
        T *ptr = new T(args...);
        return XPtr<T>(ptr, true);
    }

    /**
     * @brief Run an indicator external pointer
     *
     * @tparam Arg1 first input type
     * @tparam Args remaining input types
     * @param ptr an XPtr to indicator
     * @param arg1 first input
     * @param args remaining inputs
     * @return List
     */
    template <typename Arg1, typename... Args>
    static List run(SEXP ptr, const Arg1 &arg1, const Args &...args)
    {
        XPtr<T> c(ptr);
        R_xlen_t n = arg1.length();
        TO1 y1(n);
        TO2 y2(n);
        TO3 y3(n);
        TO4 y4(n);

        c->operator()(arg1.begin(), arg1.end(), args.begin()..., y1.begin(), y2.begin(), y3.begin(), y4.begin());
        return List::create(y1, y2, y3, y4);
    }

    /**
     * @brief Create an indicator
     *
     * @tparam Args argument types
     * @param args ctor arguments
     * @return T
     */
    template <typename... Args>
    static T create_t(Args... args)
    {
        return T(args...);
    }

    /**
     * @brief Run an indicator
     *
     * @tparam Arg1 first input type
     * @tparam Args remaining input types
     * @param c an indicator
     * @param arg1 first input
     * @param args remaining inputs
     * @return List
     */
    template <typename Arg1, typename... Args>
    static List run_t(T &c, const Arg1 &arg1, const Args &...args)
    {
        R_xlen_t n = arg1.length();
        TO1 y1(n);
        TO2 y2(n);
        TO3 y3(n);
        TO4 y4(n);

        c(arg1.begin(), arg1.end(), args.begin()..., y1.begin(), y2.begin(), y3.begin(), y4.begin());
        return List::create(y1, y2, y3, y4);
    }
};
